package nz.ac.massey.doi_information_extractor.parser;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringEscapeUtils;

import nz.ac.massey.doi_information_extractor.data.Author;
import nz.ac.massey.doi_information_extractor.data.AuthorList;
import nz.ac.massey.doi_information_extractor.data.Publication;

public class ScienceDirectPlugin implements PluginInterface {

	private String doi;
	private Publication result = new Publication();

	public ScienceDirectPlugin(String inputdoi) {
		this.doi = inputdoi;
	}

	@Override
	public void getInformationFromDOI(String doi) throws PublicationNotFoundException,
			IOException {

		URL redirectPage;
		try {
			if (doi.contains("dx.doi.org")) {
				redirectPage = new URL(doi);
			}
			else {
				redirectPage = new URL("http://dx.doi.org/" + doi);
			}
		} catch (MalformedURLException e) {
			throw new PublicationNotFoundException(e.getMessage());
		}

		URLConnection nextPage = redirectPage.openConnection();

		BufferedInputStream buffer = new BufferedInputStream(
				nextPage.getInputStream());

		StringBuilder builder = new StringBuilder();
		int byteRead;
		while ((byteRead = buffer.read()) != -1){
			builder.append((char) byteRead);
		}
		buffer.close();

		// Put the source together.
		String source = builder.toString();
		source = StringEscapeUtils.unescapeHtml4(source);

		result.setArticleTitle(getTitle(source));

		result.setAuthors(getAuthors(source));

		result.setPublicationTitle(getPublicationTitle(source));

		result.setDescription(getDescription(source));

		result.setPageNumbers(getPageNumbers(source));

		result.setDate(getDate(source));

		result.setDoi(getDOI(source));

		result.setVolumeNumber(getVolumeNumber(source));
		
		result.setEdition(getIssueNumber(source));

		result.setUrl(getUrl(source));
		
		result.setKeywords(getKeywords(source));
		
		// Set the publisher if other fields are filled. 
		if (!result.getPublicationTitle().isEmpty()) {
		result.setPublisher("Elsevier Inc.");
		}

	}
	
	private String getIssueNumber(String source) {
		String issue = "";
		Pattern issuePatt = Pattern.compile(
				"Issue .*</a>, \n\n", Pattern.UNIX_LINES);
		Matcher issueMat = issuePatt.matcher(source);
		if (issueMat.find() == true) {
			issue = source.substring(issueMat.start(), issueMat.end());
		}
		if (issue != "") {
			issue = issue.substring(6,issue.length()-8);
		}
		return issue;
	}

	private String getKeywords(String source) {
		String keywords = "";
		Pattern keywordsPatt = Pattern.compile(
				"<p><strong>Keywords: </strong>.*</p>", Pattern.UNIX_LINES);
		Matcher keywordsMat = keywordsPatt.matcher(source);
		if (keywordsMat.find() == true) {
			keywords = source.substring(keywordsMat.start(), keywordsMat.end());
		}
		if (keywords != "") {
			keywords = keywords.substring(30,keywords.length()-4);
		}
		return keywords;
	}

	private String getUrl(String source) {
		String url = "";
		Pattern urlPatt = Pattern.compile(
				"<li class=\"featureCount\">\n<a href=\"http://www.sciencedirect.com/.*\"", Pattern.UNIX_LINES);
		Matcher urlMat = urlPatt.matcher(source);
		if (urlMat.find() == true) {
			url = source.substring(urlMat.start(), urlMat.end());
		}
		if (url != "") {
			url = url.substring(35,url.length()-1);
			url = url.split("\"")[0];
		}
		return url;
	}

	private String getVolumeNumber(String source) {
		String volume = "";
		Pattern volumePatt = Pattern.compile(
				"\n\nVolume .*, Issue", Pattern.UNIX_LINES);
		Matcher volumeMat = volumePatt.matcher(source);
		if (volumeMat.find() == true) {
			volume = source.substring(volumeMat.start(), volumeMat.end());
		}
		if (volume != "") {
			volume = volume.substring(9,volume.length()-7);
		}
		return volume;
	}

	private String getDOI(String source) {
		String doiString = "";
		Pattern doiPatt = Pattern.compile(
				"<a id=\"ddDoi\" href=\".*\" target=\"doilink\"", Pattern.UNIX_LINES);
		Matcher doiMat = doiPatt.matcher(source);
		if (doiMat.find() == true) {
			doiString = source.substring(doiMat.start(), doiMat.end());
		}
		if (doiString != "") {
			doiString = doiString.substring(20,doiString.length()-18);
		}
		return doiString;
	}

	private String getDate(String source) {
		String date = "";
		Pattern datePatt = Pattern.compile(
				"Volume.*, Issue.*</a>, \n\n\n.*,\n\n\nPages", Pattern.UNIX_LINES);
		Matcher dateMat = datePatt.matcher(source);
		if (dateMat.find() == true) {
			date = source.substring(dateMat.start(), dateMat.end());
		}
		if (date != "") {
			date = date.split("\n")[3];
			date = date.substring(0,date.length()-1);
		}
		return date;
	}

	private String getPageNumbers(String source) {
		String pages = "";
		Pattern pagesPatt = Pattern.compile(
				"\n\n\nPages .*\n", Pattern.UNIX_LINES);
		Matcher pagesMat = pagesPatt.matcher(source);
		if (pagesMat.find() == true) {
			pages = source.substring(pagesMat.start(), pagesMat.end());
		}
		if (pages != "") {
			pages = pages.split("\n")[3];
			pages = pages.substring(6, pages.length());
			pages = pages.replace("<br>", "");
		}
		return pages;
	}

	private String getTitle(String source) {
		String articleTitle = "";
		Pattern titlePatt = Pattern.compile(
				"<div class=\"articleTitle\">\n.*</div>", Pattern.UNIX_LINES);
		Matcher titleMat = titlePatt.matcher(source);
		if (titleMat.find() == true) {
			articleTitle = source.substring(titleMat.start(), titleMat.end());
		}
		if (articleTitle != "") {
			articleTitle = articleTitle
					.substring(27, articleTitle.length() - 7);
		}

		return articleTitle;
	}

	private AuthorList getAuthors(String source) {
		AuthorList authorsList = new AuthorList();
		// This will represent the string containing the authors names, in some
		// of the intermediate stages.
		String authorsString = "";
		// Snatch the correct section out of the website.
		Pattern authorsPatt = Pattern.compile("<p><strong>.*</strong></p>",
				Pattern.UNIX_LINES);
		Matcher authorsMat = authorsPatt.matcher(source);
		while (authorsMat.find()) {
			authorsString = source.substring(authorsMat.start(),
					authorsMat.end());
		}
		if (authorsString != "") {
			// Trim and do the intermediate processing to make the string tidy
			// at each end.
			authorsString = authorsString.substring(11,
					authorsString.length() - 13);

			// This string will eventually hold a string containing only the
			// authors names, organisation reference, and possibly the word
			// "and".
			String authorsFurtherSplitString = authorsString;

			// This chops out all the anchor tags. Organisation assigning could
			// be done here somehow, but I chose not to. Instead, it will leave
			// just the number of the organisation, and the text representing
			// the name of the organisation will be added to the authors later.
			authorsFurtherSplitString = authorsFurtherSplitString.replaceAll("</a>", "");
			authorsFurtherSplitString = authorsFurtherSplitString.replaceAll("<snm>","");
			authorsFurtherSplitString = authorsFurtherSplitString.replaceAll("</snm>","");
			
			// This chops out all the <sup>.*</sup> sections. There isn't any
			// interesting information in them anyway.
			while (authorsFurtherSplitString.contains("<sup>")) {
				String authorsStringRemoveSups = authorsFurtherSplitString
						.substring(authorsFurtherSplitString.indexOf("<sup>"),
								authorsFurtherSplitString.indexOf("</sup") + 6);
				authorsFurtherSplitString = authorsFurtherSplitString
						.replaceFirst(authorsStringRemoveSups, "");
			}

			// This chops out all the <!-- .* --> sections. There isn't any
			// interesting information in them anyway.
			while (authorsFurtherSplitString.contains("<!--")) {
				String authorsStringRemoveComments = authorsFurtherSplitString
						.substring(authorsFurtherSplitString.indexOf("<!--"),
								authorsFurtherSplitString.indexOf("-->") + 3);
				authorsFurtherSplitString = authorsFurtherSplitString
						.replaceFirst(authorsStringRemoveComments, "");
			}
			
			while (authorsFurtherSplitString.contains("<a")) {
				String authorsStringRemoveAs = authorsFurtherSplitString
						.substring(authorsFurtherSplitString.indexOf("<a"),
								authorsFurtherSplitString.indexOf(">") + 1);
				if (authorsStringRemoveAs.contains("a href=\"#implicit")) {
					authorsFurtherSplitString = authorsFurtherSplitString
							.replace(
									authorsStringRemoveAs,
									" "
											+ authorsStringRemoveAs.substring(
													(authorsStringRemoveAs
															.indexOf("a href=\"#implicit") + 17),
													(authorsStringRemoveAs
															.indexOf("a href=\"#implicit") + 18)));
				} else if (authorsStringRemoveAs.contains("a href=\"#aff00")) {
					authorsFurtherSplitString = authorsFurtherSplitString
							.replace(
									authorsStringRemoveAs,
									" "
											+ authorsStringRemoveAs.substring(
													(authorsStringRemoveAs
															.indexOf("a href=\"#aff00") + 14),
													(authorsStringRemoveAs
															.indexOf("a href=\"#aff00") + 15)));
				} else if (authorsStringRemoveAs.contains("a href=\"#aff")) {
					authorsFurtherSplitString = authorsFurtherSplitString
							.replace(
									authorsStringRemoveAs,
									" "
											+ authorsStringRemoveAs.substring(
													(authorsStringRemoveAs
															.indexOf("a href=\"#aff") + 12),
													(authorsStringRemoveAs
															.indexOf("a href=\"#aff") + 13)));
				}
					else {
					authorsFurtherSplitString = authorsFurtherSplitString
							.replace(authorsStringRemoveAs, "");
				}
			}
			
			// Tokenize the remaining string, which should only have authors
			// names and possibly the word "and".
			String[] smallerNameDivisions = authorsFurtherSplitString
					.split(" ");

			// This is a little more tricky than the other methods, as it has to
			// fill the AuthorList with Author objects. It will make mistakes if
			// someone is called 'and', but does work for one middle name!
			Author tempAuthor = new Author();
			for (int j = 0; j < smallerNameDivisions.length; j++) {
				
				// If the word is "and", skip it.
				if (smallerNameDivisions[j].equals("and")) {
					continue;
				}
				
				// Otherwise, if there's no first name yet, the word must be a
				// first name.
				if (tempAuthor.getAuthorFirstName() == "") {
					tempAuthor.setAuthorFirstName(smallerNameDivisions[j]);
				} 
				// If there's a first name but no last name, it must be the
				// last name.
				else if (tempAuthor.getAuthorLastName() == "") {
					tempAuthor.setAuthorLastName(smallerNameDivisions[j]);
				} 
				// Otherwise, it's possible this is a second part of the last name.. If so, check the next item defines an organisation.
				if (tempAuthor.getAuthorFirstName() != "" && tempAuthor.getAuthorLastName() != "" && j+2 < smallerNameDivisions.length && Character.isDigit(smallerNameDivisions[j+2].charAt(0))) {
					tempAuthor.setAuthorLastName(tempAuthor.getAuthorLastName().concat(" " + smallerNameDivisions[j+1]));
					j++;
				}
				// If the word is just a digit, it refers to the organisation of
				// the author, which means the first and last names have already
				// been inputted, and it's time to add the completed author to
				// the list.
				if ( j+1 < smallerNameDivisions.length && smallerNameDivisions[j+1] != null && Character.isDigit(smallerNameDivisions[j+1].charAt(0))) {
					tempAuthor.setOrganisation(String.valueOf(smallerNameDivisions[j+1].charAt(0)));
					j++;
				}
				
				// It's possible that the first and last names are already filled
				// but there is no organisation. If so, this should add the
				// author to the list anyway.
				if (tempAuthor.getAuthorFirstName() != "" && tempAuthor.getAuthorLastName() != "") {
				authorsList.add(tempAuthor);
				tempAuthor = new Author();
				}
			}

			// Code here will relate the "aff" id code (e.g.<a
			// href="#aff1"...) each author currently has with the
			// organisation it represents.
			authorsPatt = Pattern.compile("<p><a name=\"aff.\"></a><sup>.</sup>.*</p>",
					Pattern.UNIX_LINES);
			authorsMat = authorsPatt.matcher(source);
			ArrayList<String> organisationList = new ArrayList<String>();
			String[] organisationTempStrings = new String[1];
			if (authorsMat.find()) {
				organisationTempStrings = source.substring(authorsMat.start()+3,
						authorsMat.end()).trim().split("<p>");
				for (int i = 0; i < organisationTempStrings.length; i++) {
					organisationList.add(organisationTempStrings[i].substring(31,organisationTempStrings[i].length()-4));
				}
			}
			if (!organisationList.isEmpty()) {
				for (int i = 0; i < authorsList.size(); i++) {
					Author orgAuthor = authorsList.get(i);
					String organisationNumber = orgAuthor.getOrganisation()
							.replace(",", "");
					try {
						int orgNumber = Integer.parseInt(organisationNumber);
						orgAuthor.setOrganisation(organisationList
								.get(orgNumber - 1));
						authorsList.set(i, orgAuthor);
					} catch (NumberFormatException e) {
						continue;
					}
				}
			}
			
			// Code here will relate the "aff00" id code (e.g.<a
			// href="#aff001"...) each author currently has with the
			// organisation it represents.
			authorsPatt = Pattern.compile("<p><a name=\"aff00.\"></a><sup>.</sup>.*</p>",
					Pattern.UNIX_LINES);
			authorsMat = authorsPatt.matcher(source);
			if (authorsMat.find()) {
				organisationTempStrings = source.substring(authorsMat.start()+3,
						authorsMat.end()).trim().split("<p>");
				for (int i = 0; i < organisationTempStrings.length; i++) {
					organisationList.add(organisationTempStrings[i].substring(33,organisationTempStrings[i].length()-4));
				}
			}
			if (!organisationList.isEmpty()) {
				for (int i = 0; i < authorsList.size(); i++) {
					Author orgAuthor = authorsList.get(i);
					String organisationNumber = orgAuthor.getOrganisation()
							.replace(",", "");
					try {
						int orgNumber = Integer.parseInt(organisationNumber);
						orgAuthor.setOrganisation(organisationList
								.get(orgNumber - 1));
						authorsList.set(i, orgAuthor);
					} catch (NumberFormatException e) {
						continue;
					}
				}
			}
			
			// Code here will relate the "implicit" id code (e.g.<a
			// href="#implicit0"...) each author currently has with the
			// organisation it represents.
			authorsPatt = Pattern.compile("<a name=\"implicit.*</p>",
					Pattern.UNIX_LINES);
			authorsMat = authorsPatt.matcher(source);
			organisationList = new ArrayList<String>(); 
			while (authorsMat.find()) {
				organisationList.add(source.substring(authorsMat.start() + 36,
						authorsMat.end() - 4).trim());
			}
			if (!organisationList.isEmpty()) {
				for (int i = 0; i < authorsList.size(); i++) {
					Author orgAuthor = authorsList.get(i);
					String organisationNumber = orgAuthor.getOrganisation()
							.replace(",", "");
					try {
						int orgNumber = Integer.parseInt(organisationNumber);
						orgAuthor.setOrganisation(organisationList
								.get(orgNumber));
						authorsList.set(i, orgAuthor);
					} catch (NumberFormatException e) {
						continue;
					}
				}
			}
		}
		return authorsList;
	}

	private String getPublicationTitle(String source) {
		String publicationString = "";
		// Snatch the correct section out of the website.
		Pattern pubPatt = Pattern.compile("<a id=\"ddJrnl\".*><b>.*</b></a>",
				Pattern.UNIX_LINES);
		Matcher pubMat = pubPatt.matcher(source);
		while (pubMat.find()) {
			publicationString = source.substring(pubMat.start(), pubMat.end());
		}
		if (publicationString != "") {
			publicationString = publicationString.substring(51,
					publicationString.length() - 8);
		}
		return publicationString;
	}

	private String getDescription(String source) {
		String descriptionString = "";
		// Snatch the correct section out of the website.
		Pattern descPatt = Pattern.compile(
				"<h3 class=\"h3\">Abstract</h3>.*<p>.*<!-- articleText -->",
				Pattern.UNIX_LINES);
		Matcher descMat = descPatt.matcher(source);
		while (descMat.find()) {
			descriptionString = source
					.substring(descMat.start(), descMat.end());
		}
		if (descriptionString != "") {
			descriptionString = descriptionString.substring(28,
					descriptionString.length() - 32);
			// Cut paragraph tags out. Assumes they're properly balanced.
			while (descriptionString.contains("<p>")) {
				descriptionString = descriptionString.substring(0,
						descriptionString.indexOf("<p>"))
						+ descriptionString.substring(
								descriptionString.indexOf("<p>") + 3,
								descriptionString.length());
				descriptionString = descriptionString.substring(0,
						descriptionString.indexOf("</p>"))
						+ " "
						+ descriptionString.substring(
								descriptionString.indexOf("</p>") + 4,
								descriptionString.length());
			}
			while (descriptionString.contains("<a")) {
				String descriptionStringRemoveAs = descriptionString
				.substring(descriptionString.indexOf("<a"),descriptionString.indexOf("</a>") + 4);
				descriptionString = descriptionString.replace(descriptionStringRemoveAs, "");
			}
			// How long was the description field again?
			if (descriptionString.length() > 1012) {
				descriptionString = descriptionString.substring(0, 1012);
			}
		}
		return descriptionString;
	}



	@Override
	public Publication call() throws Exception {
		getInformationFromDOI(doi);
		return result;
	}
}
